package cn.thornbird.orgsync.helper;

import cn.thornbird.orgsync.entity.status.AppType;
import cn.thornbird.orgsync.helper.converter.WechatConverter;
import cn.thornbird.orgsync.model.AppUserData;
import cn.thornbird.orgsync.model.TreeData;
import cn.thornbird.orgsync.model.wechat.*;
import cn.thornbird.orgsync.util.JsonUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;

import java.util.*;
import java.util.stream.Collectors;

public class WechatHelper extends AppHelper<UserList.User> {

    public static final String API_HOST = "https://qyapi.weixin.qq.com/cgi-bin";

    /**
     * <a href="https://developer.work.weixin.qq.com/document/path/91039">获取access_token</a>
     */
    public static final String GET_TOKEN_PATH = "/gettoken?corpid={corpid}&corpsecret={corpsecret}";

    /**
     * <a href="https://developer.work.weixin.qq.com/document/path/90208">获取部门列表</a>
     */
    public static final String GET_DEPARTMENT_LIST_PATH = "/department/list?access_token={access_token}&id={id}";

    /**
     * <a href="https://developer.work.weixin.qq.com/document/path/90201">获取部门成员详情</a>
     */
    public static final String GET_USER_LIST_PATH = "/user/list?access_token={access_token}&department_id={department_id}&fetch_child={fetch_child}";

    /**
     * <a href="https://developer.work.weixin.qq.com/document/path/90196">读取成员</a>
     */
    public static final String GET_USER_PATH = "/user/get?access_token={access_token}&userid={userid}";

    /**
     * <a href="https://developer.work.weixin.qq.com/document/path/95402">手机号获取userid</a>
     */
    public static final String GET_USER_ID_BY_MOBILE_PATH = "/user/getuserid?access_token={access_token}";

    /**
     * <a href="https://developer.work.weixin.qq.com/document/path/95895">邮箱获取userid</a>
     */
    public static final String GET_USER_ID_BY_EMAIL_PATH = "/user/get_userid_by_email?access_token={access_token}";

    /**
     * <a href="https://developer.work.weixin.qq.com/document/path/90205">创建部门</a>
     */
    public static final String CREATE_DEPARTMENT_PATH = "/department/create?access_token={access_token}";

    /**
     * <a href="https://developer.work.weixin.qq.com/document/path/90206">更新部门</a>
     */
    public static final String UPDATE_DEPARTMENT_PATH = "/department/update?access_token={access_token}";

    /**
     * <a href="https://developer.work.weixin.qq.com/document/path/90207">删除部门</a>
     */
    public static final String DELETE_DEPARTMENT_PATH = "/department/delete?access_token={access_token}&id={id}";

    /**
     * <a href="https://developer.work.weixin.qq.com/document/path/90195">创建成员</a>
     */
    public static final String CREATE_USER_PATH = "/user/create?access_token={access_token}";

    /**
     * <a href="https://developer.work.weixin.qq.com/document/path/90197">更新成员</a>
     */
    public static final String UPDATE_USER_PATH = "/user/update?access_token={access_token}";

    /**
     * <a href="https://developer.work.weixin.qq.com/document/path/90198">删除成员</a>
     */
    public static final String DELETE_USER_PATH = "/user/delete?access_token={access_token}&userid={userid}";

    @Override
    public String getToken() {
        return getToken(false);
    }

    public String getToken(boolean addressBook) {
        String token = new AccessTokenFactory().getAccessToken(AppType.Wechat, certificateText).getToken(redisTemplate);
        return token.split(" ")[addressBook ? 1 : 0];
    }

    @Override
    public List<TreeData> getOrganizationTree() {
        List<TreeData> treeDataList = new ArrayList<>();
        Map<String, Object> uriVariables = new HashMap<>();
        uriVariables.put("id", 1L);
        DepartmentList departmentList = doGet(GET_DEPARTMENT_LIST_PATH, uriVariables, getToken(), DepartmentList.class);
        if (departmentList.isSuccess()) {
            List<DepartmentList.Department> departments = departmentList.getDepartments().stream().sorted(
                    Comparator.comparing(DepartmentList.Department::getParentId).thenComparing(
                            DepartmentList.Department::getOrder).reversed()
            ).collect(Collectors.toList());
            generateOrganizationTree(treeDataList, departments, null);
        }
        return treeDataList;
    }

    @Override
    public List<AppUserData> getUsersByDepartmentId(String departmentId) {
        List<AppUserData> users = new ArrayList<>();
        UserList userList = getUserList(departmentId == null ? null : Long.parseLong(departmentId), false);
        if (userList.isSuccess() && userList.getUsers() != null) {
            WechatConverter converter = new WechatConverter();
            for (UserList.User user : userList.getUsers()) {
                users.add(converter.parseUser(user));
            }
        }
        return users;
    }

    @Override
    public AppUserData findAppUser(AppUserData sourceUser) {
        AppUserData targetUser = null;
        UserList.User user = getUser(UserAttr.MOBILE, sourceUser.getMobile());
        if (user == null) {
            user = getUser(UserAttr.EMAIL, sourceUser.getEmail());
        }
        if (user == null) {
            user = getUser(UserAttr.EMPLOYEE_NO, sourceUser.getEmployeeNo());
        }
        if (user == null) {
            user = getUser(UserAttr.NAME, sourceUser.getName());
        }
        if (user != null) {
            targetUser = new WechatConverter().parseUser(user);
        }
        return targetUser;
    }

    @Override
    public AppUserData findAppUserById(String id) {
        AppUserData targetUser = null;
        UserList.User user = getUser(UserAttr.ID, id);
        if (user != null) {
            targetUser = new WechatConverter().parseUser(user);
        }
        return targetUser;
    }

    public DepartmentList getDepartmentList(Long departmentId) {
        String token = getToken();
        Map<String, Object> uriVariables = new HashMap<>();
        departmentId = departmentId == null ? 1L : departmentId;
        uriVariables.put("id", departmentId);
        DepartmentList departmentList = doGet(GET_DEPARTMENT_LIST_PATH, uriVariables, token, DepartmentList.class);
        if (departmentList.isSuccess()) {
            for (int i = 0, count = departmentList.getDepartments().size(); i < count; i++) {
                if (departmentList.getDepartments().get(i).getId().equals(departmentId)) {
                    departmentList.getDepartments().remove(i);
                    break;
                }
            }
        }
        return departmentList;
    }

    public List<Long> getDepartmentIds(Long departmentId, DepartmentList departmentList) {
        List<Long> departmentIds = new ArrayList<>();
        departmentId = departmentId == null ? 1L : departmentId;
        departmentIds.add(departmentId);
        if (departmentList.isSuccess() && departmentList.getDepartments() != null) {
            departmentIds = departmentList.getDepartments().stream().map(
                    DepartmentList.Department::getId).collect(Collectors.toList());
            departmentIds.add(0, departmentId);
        }
        return departmentIds;
    }

    public UserList getUserList(Long departmentId, boolean fetchChild) {
        String token = getToken();
        Map<String, Object> uriVariables = new HashMap<>();
        uriVariables.put("department_id", departmentId == null ? 1L : departmentId);
        uriVariables.put("fetch_child", fetchChild ? "1" : "0");
        return doGet(GET_USER_LIST_PATH, uriVariables, token, UserList.class);
    }

    public UserList.User getUser(UserAttr userAttr, Object value) {
        UserList.User user = null;
        if (userAttr == null || value == null || StringUtils.isEmpty(value.toString())) {
            return user;
        }

        String token = getToken();
        UserDetail userDetail = null;
        UserId userId = null;
        Map<String, Object> uriVariables = new HashMap<>();
        Map<String, Object> params = new HashMap<>();
        switch (userAttr) {
            case ID:
                uriVariables.put("userid", value);
                userDetail = doGet(GET_USER_PATH, uriVariables, token, UserDetail.class);
                break;
            case NAME:
            case MOBILE:
                params.put("mobile", getPureMobile(value.toString()));
                userId = doPost(GET_USER_ID_BY_MOBILE_PATH, params, token, UserId.class);
                break;
            case EMAIL:
                params.put("email", value);
                params.put("email_type", 2);
                userId = doPost(GET_USER_ID_BY_EMAIL_PATH, params, token, UserId.class);
                break;
            case ENTERPRISE_EMAIL:
                params.put("email", value);
                params.put("email_type", 1);
                userId = doPost(GET_USER_ID_BY_EMAIL_PATH, params, token, UserId.class);
                break;
            case EMPLOYEE_NO:
            default:
                break;
        }
        if (userId != null && userId.isSuccess()) {
            uriVariables.put("userid", userId.getUserId());
            userDetail = doGet(GET_USER_PATH, uriVariables, token, UserDetail.class);
        }
        if (userDetail != null) {
            if (userDetail.isSuccess()) {
                user = JsonUtil.parseObject(JsonUtil.toJsonString(userDetail), UserList.User.class);
            }
        }
        if (userAttr == UserAttr.EMPLOYEE_NO || userAttr == UserAttr.NAME) {
            user = findUser(getAllUsers(), userAttr, value, userAttr == UserAttr.NAME);
        }
        return user;
    }

    public List<UserList.User> getAllUsers() {
        List<UserList.User> users = null;
        if (allUsers == null) {
            UserList userList = getUserList(null, true);
            if (userList.isSuccess()) {
                users = userList.getUsers();
                allUsers = users;
            }
        } else {
            users = allUsers;
        }
        return users;
    }

    public IdResponse createDepartment(String name, String englishName, Long parentId, Long id, Long order) {
        String token = getToken(true);
        Map<String, Object> params = new HashMap<>();
        params.put("name", name);
        params.put("parentid", parentId == null ? 1L : parentId);
        if (!StringUtils.isEmpty(englishName)) {
            params.put("name_en", englishName);
        }
        if (id != null) {
            params.put("id", id);
        }
        if (order != null) {
            params.put("order", order);
        }
        return doPost(CREATE_DEPARTMENT_PATH, params, token, IdResponse.class);
    }

    public Response updateDepartment(Long id, String name, String englishName, Long parentId, Long order) {
        String token = getToken(true);
        Map<String, Object> params = new HashMap<>();
        params.put("id", id);
        if (!StringUtils.isEmpty(name)) {
            params.put("name", name);
        }
        if (!StringUtils.isEmpty(englishName)) {
            params.put("name_en", englishName);
        }
        if (parentId != null) {
            params.put("parentid", parentId);
        }
        if (order != null) {
            params.put("order", order);
        }
        return doPost(UPDATE_DEPARTMENT_PATH, params, token, Response.class);
    }

    public Response deleteDepartment(Long id) {
        DepartmentList departmentList = getDepartmentList(id);
        List<Long> departmentIds = getDepartmentIds(id, departmentList);
        UserList userList = getUserList(id, true);
        if (userList != null && userList.getUsers() != null) {
            for (UserList.User user : userList.getUsers()) {
                List<Long> currentDepartmentIds = new ArrayList<>();
                List<Long> userDepartmentIds = user.getDepartment();
                if (userDepartmentIds != null) {
                    for (Long departmentId : userDepartmentIds) {
                        if (!departmentIds.contains(departmentId)) {
                            currentDepartmentIds.add(departmentId);
                        }
                    }
                }
                if (currentDepartmentIds.isEmpty()) {
                    currentDepartmentIds.add(1L);
                }
                UserInfo userInfo = new UserInfo();
                userInfo.setUserId(user.getUserId());
                userInfo.setDepartment(currentDepartmentIds);
                updateUser(userInfo);
            }
        }
        if (departmentList.isSuccess() && departmentList.getDepartments() != null) {
            deleteSubdepartments(departmentList, id, id);
        }
        String token = getToken(true);
        Map<String, Object> uriVariables = new HashMap<>();
        uriVariables.put("id", id);
        return doGet(DELETE_DEPARTMENT_PATH, uriVariables, token, Response.class);
    }

    public CreateUserResponse createUser(UserInfo userInfo) {
        return doPost(CREATE_USER_PATH, userInfo.toCreateParams(), getToken(true), CreateUserResponse.class);
    }

    public Response updateUser(UserInfo userInfo) {
        return doPost(UPDATE_USER_PATH, userInfo.toUpdateParams(), getToken(true), Response.class);
    }

    public Response deleteUser(String userId) {
        String token = getToken(true);
        Map<String, Object> uriVariables = new HashMap<>();
        uriVariables.put("userid", userId);
        return doGet(DELETE_USER_PATH, uriVariables, token, Response.class);
    }

    public void removeCircularReference(String userId, String leaderId) {
        if (StringUtils.isEmpty(userId) || StringUtils.isEmpty(leaderId)) {
            return;
        }

        List<UserList.User> users = getAllUsers();
        UserList.User user = findUser(users, UserAttr.ID, userId);
        if (user == null) {
            return;
        }

        UserList.User leader;
        while (true) {
            leader = findUser(users, UserAttr.ID, leaderId);
            if (leader == null || leader.getDirectLeader() == null || leader.getDirectLeader().isEmpty()) {
                break;
            }
            if (leader.getDirectLeader().contains(userId)) {
                UserInfo userInfo = new UserInfo();
                userInfo.setUserId(leaderId);
                userInfo.setDirectLeader(new ArrayList<>());
                updateUser(userInfo);
                break;
            }
            leaderId = leader.getDirectLeader().get(0);
        }
    }

    public <T> T doGet(String uri, Map<String, Object> uriVariables, String token, Class<T> clazz) {
        if (uriVariables != null && !StringUtils.isEmpty(token)) {
            uriVariables.put("access_token", token);
        }
        return doGet(API_HOST + uri, uriVariables, clazz);
    }

    public <T> T doPost(String uri, Map<String, Object> params, String token, Class<T> clazz) {
        HttpHeaders headers = new HttpHeaders();
        return doPost(formatUri(API_HOST + uri, token), headers, params, clazz);
    }

    @Override
    protected boolean checkUser(UserList.User user, UserAttr userAttr, Object value) {
        boolean result = false;
        switch (userAttr) {
            case ID:
                if (user.getUserId().equals(value)) {
                    result = true;
                }
                break;
            case NAME:
                if (user.getName().equalsIgnoreCase(value.toString())) {
                    result = true;
                }
                break;
            case MOBILE:
                result = compareMobile(user.getMobile(), (String) value);
                break;
            case EMAIL:
                if (user.getEmail() != null && user.getEmail().equalsIgnoreCase(value.toString())) {
                    result = true;
                }
                break;
            case ENTERPRISE_EMAIL:
                if (user.getBizEmail() != null && user.getBizEmail().equalsIgnoreCase(value.toString())) {
                    result = true;
                }
                break;
            case EMPLOYEE_NO:
            default:
                break;
        }
        return result;
    }

    private void generateOrganizationTree(List<TreeData> treeDataList, List<DepartmentList.Department> departments,
                                          TreeData parent) {
        for (int i = 0, count = departments.size(); i < count; i++) {
            DepartmentList.Department department = departments.get(i);
            if (department.getParentId().equals(parent == null ? 0L : Long.parseLong(parent.getKey()))) {
                TreeData item = new TreeData();
                item.setKey(department.getId().toString());
                item.setTitle(department.getName());
                if (parent == null) {
                    treeDataList.add(item);
                } else {
                    if (parent.getChildren() == null) {
                        parent.setChildren(new ArrayList<>());
                    }
                    parent.getChildren().add(item);
                }
                generateOrganizationTree(treeDataList, departments, item);
            }
        }
    }

    private void deleteSubdepartments(DepartmentList departmentList, Long parentId, Long rootId) {
        List<Long> ids = new ArrayList<>();
        for (DepartmentList.Department department : departmentList.getDepartments()) {
            if (department.getParentId() != null && department.getParentId().equals(parentId)) {
                ids.add(department.getId());
            }
        }
        for (Long id : ids) {
            deleteSubdepartments(departmentList, id, rootId);
        }
        if (!parentId.equals(rootId)) {
            Map<String, Object> uriVariables = new HashMap<>();
            uriVariables.put("id", parentId);
            doGet(DELETE_DEPARTMENT_PATH, uriVariables, getToken(true), Response.class);
        }
    }

}
